package templates

import (
	"context"
	"github.com/stretchr/testify/require"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"reflect"
	"testing"
)

func TestFromYAML(t *testing.T) {
	tests := []struct {
		name    string
		yaml    string
		want    interface{}
		wantErr bool
	}{
		{
			"invalid yaml",
			"'",
			nil,
			true,
		},
		{
			"empty object",
			"{}",
			map[string]interface{}{},
			false,
		},
		{
			"raw string",
			"hello world",
			"hello world",
			false,
		},
		{
			"array of strings",
			`["hello", "goodbye"]`,
			[]interface{}{"hello", "goodbye"},
			false,
		},
		{
			"array of objects",
			`["hello", {}]`,
			[]interface{}{"hello", map[string]interface{}{}},
			false,
		},
		{
			"top-level object",
			`age: "23"
hello: bar`,
			map[string]interface{}{"age": "23", "hello": "bar"},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := FromYAML(tt.yaml)
			if tt.wantErr {
				require.Error(t, err, "Expected error unmarshalling yaml string")
				return
			}

			require.NoError(t, err, "Unexpected error unmarshalling yaml string")
			require.Equal(t, tt.want, got)
		})
	}
}

func TestToYAML(t *testing.T) {
	type args struct {
		resource interface{}
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr bool
	}{
		{
			name: "ok",
			args: args{resource: map[string]interface{}{"bobob": 123, "bubu": map[string]interface{}{"lolo": "pufpuf"}}},
			want: `bobob: 123
bubu:
  lolo: pufpuf
`,
			wantErr: false,
		},
		{
			name:    "empty",
			args:    args{},
			want:    "",
			wantErr: false,
		},
	}

	logs.Logger = zap.NewNop()

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ToYAML(tt.args.resource)
			if (err != nil) != tt.wantErr {
				t.Errorf("ToYAML() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ToYAML() got = %v, \nwant %v", []byte(got), []byte(tt.want))
			}
		})
	}
}

func TestNindent(t *testing.T) {
	type args struct {
		in        string
		spacesNum int
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "simple",
			args: args{
				in: `some:
  yaml:
    simple:
      string`,
				spacesNum: 2,
			},
			want: `
  some:
    yaml:
      simple:
        string
`,
		},
		{
			name: "not-so-simple",
			args: args{
				in: `some:
  yaml:
    not-so-simple:
      - strings
      - strings`,
				spacesNum: 0,
			},
			want: `
some:
  yaml:
    not-so-simple:
      - strings
      - strings
`,
		},
		{
			name: "empty",
			args: args{
				in:        "",
				spacesNum: 0,
			},
			want: "\n\n",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Nindent(tt.args.in, tt.args.spacesNum); got != tt.want {
				t.Errorf("Nindent() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestIndent(t *testing.T) {
	type args struct {
		in        string
		spacesNum int
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "simple",
			args: args{
				in: `some:
  yaml:
    simple:
      string`,
				spacesNum: 2,
			},
			want: `some:
    yaml:
      simple:
        string
`,
		},
		{
			name: "not-so-simple",
			args: args{
				in: `some:
  yaml:
    not-so-simple:
      - strings
      - strings`,
				spacesNum: 0,
			},
			want: `some:
  yaml:
    not-so-simple:
      - strings
      - strings
`,
		},
		{
			name: "empty",
			args: args{
				in:        "",
				spacesNum: 0,
			},
			want: "\n",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Indent(tt.args.in, tt.args.spacesNum); got != tt.want {
				t.Errorf("Indent() = \n%v, want \n%v", got, tt.want)
			}
		})
	}
}

func TestLoadTemplate(t *testing.T) {
	type args struct {
		ctx context.Context
		ts  []v1alpha1.Template
	}
	tests := []struct {
		name     string
		args     args
		wantName string
	}{
		{
			name: "Normal Template",
			args: args{
				ctx: context.Background(),
				ts: []v1alpha1.Template{
					{
						TypeMeta: metav1.TypeMeta{
							Kind:       "Template",
							APIVersion: "kubelatte.io/v1alpha1",
						},
						ObjectMeta: metav1.ObjectMeta{},
						Spec: v1alpha1.TemplateSpec{
							Kind:       "Pod",
							ApiVersion: "v1",
							Data:       "{{data}}",
							Type:       "mutation",
						},
					},
				},
			},
			wantName: "common",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := LoadTemplate(tt.args.ctx, tt.args.ts...); !reflect.DeepEqual(got.Name(), tt.wantName) {
				t.Errorf("LoadTemplate() = %v, want %v", got, tt.wantName)
			}
		})
	}
}

func TestReplaceKey(t *testing.T) {
	type args struct {
		oldK   string
		newKey string
		in     any
	}
	tests := []struct {
		name string
		args args
		want any
	}{
		{
			name: "test",
			args: args{
				oldK:   "foo",
				newKey: "bar",
				in: map[string]interface{}{
					"metadata": map[string]interface{}{
						"annotations": map[string]string{
							"test": "test",
							"foo":  "bar",
						},
					},
				},
			},
			want: map[string]interface{}{
				"metadata": map[string]interface{}{
					"annotations": map[string]string{
						"test": "test",
						"foo":  "bar",
					},
				},
			},
		},
		{
			name: "wrong input",
			args: args{
				oldK:   "foo",
				newKey: "bar",
				in:     "foo",
			},
			want: "foo",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()
			if got := ReplaceKey(tt.args.oldK, tt.args.newKey, tt.args.in); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("ReplaceKey() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestDel(t *testing.T) {
	type args struct {
		path string
		obj  any
	}
	tests := []struct {
		name string
		args args
		want any
	}{
		{
			name: "test",
			args: args{
				path: "metadata.annotations.test",
				obj: map[string]interface{}{
					"metadata": map[string]interface{}{
						"annotations": map[string]string{
							"test": "test",
							"foo":  "bar",
						},
					},
				},
			},
			want: map[string]interface{}{
				"metadata": map[string]interface{}{
					"annotations": map[string]string{
						"test": "test",
						"foo":  "bar",
					},
				},
			},
		},
		{
			name: "correct",
			args: args{
				path: "spec.volumes.test",
				obj: map[string]interface{}{
					"spec": map[string]interface{}{
						"volumes": map[string]interface{}{
							"test": "test",
							"foo":  "foo",
							"bar":  "bar",
						},
					},
				},
			},
			want: map[string]interface{}{
				"spec": map[string]interface{}{
					"volumes": map[string]interface{}{
						"foo": "foo",
						"bar": "bar",
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()
			if got := Del(tt.args.path, tt.args.obj); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Del() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_replaceRBACTypedConfig(t *testing.T) {
	type args struct {
		in         interface{}
		filterType string
	}
	tests := []struct {
		name    string
		args    args
		want    interface{}
		wantErr bool
	}{
		{
			name: "wrong syntax",
			args: args{
				in:         "test",
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "without patch",
			args: args{
				in: map[string]interface{}{
					"foo": "bar"},
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "patch wrong type",
			args: args{
				in: map[string]interface{}{
					"patch": "bar"},
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "without patch.value",
			args: args{
				in: map[string]interface{}{
					"patch": map[string]interface{}{
						"foo": "bar"}},
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "with patch.value.config",
			args: args{
				in: map[string]interface{}{
					"patch": map[string]interface{}{
						"value": map[string]interface{}{
							"config": map[string]interface{}{
								"foo": "bar"},
							"typed_config": map[string]interface{}{
								"bar": "foo"}}}},
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "with patch.value.config.rules.policies.permissions",
			args: args{
				in: map[string]interface{}{
					"patch": map[string]interface{}{
						"value": map[string]interface{}{
							"config": map[string]interface{}{
								"rules": map[string]interface{}{
									"policies": map[string]interface{}{
										"permissions": []interface{}{
											map[string]interface{}{"permission": "1"}}}}}}}},
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "with patch.value.config.rules.policies.permission",
			args: args{
				in: map[string]interface{}{
					"patch": map[string]interface{}{
						"value": map[string]interface{}{
							"config": map[string]interface{}{
								"rules": map[string]interface{}{
									"policies": map[string]interface{}{
										"permissions": map[string]interface{}{
											"foo": 1}}}}}}},
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "wrong permissions format",
			args: args{
				in: map[string]interface{}{
					"patch": map[string]interface{}{
						"value": map[string]interface{}{
							"config": map[string]interface{}{
								"rules": map[string]interface{}{
									"policies": map[string]interface{}{
										"permissions": map[string]interface{}{
											"permissions": map[string]interface{}{
												"bar": "bar"}}}}}}}},
				filterType: "es",
			},
			want:    nil,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := replaceRBACTypedConfig(tt.args.in, tt.args.filterType)
			if (err != nil) != tt.wantErr {
				t.Errorf("replaceRBACTypedConfig() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("replaceRBACTypedConfig() got = %v, want %v", got, tt.want)
			}
		})
	}

	t.Run("ok", func(t *testing.T) {
		in := map[string]interface{}{
			"patch": map[string]interface{}{
				"value": map[string]interface{}{
					"config": map[string]interface{}{
						"rules": map[string]interface{}{
							"policies": map[string]interface{}{
								"permissions": map[string]interface{}{
									"permissions": map[string]interface{}{
										"bar": "bar"},
									"principals": map[string]interface{}{
										"foo": "bar"}}}}}}}}

		got, err := replaceRBACTypedConfig(in, "es")
		if err != nil {
			t.Errorf("replaceRBACTypedConfig() error = %v, expected no error", err)
			return
		}
		if !reflect.DeepEqual(got, in) {
			t.Errorf("replaceRBACTypedConfig() got = %v, want %v", got, in)
		}
	})
}

func Test_replaceFilterChainName(t *testing.T) {

	t.Run("ok", func(t *testing.T) {
		out := map[string]interface{}{
			"match": map[string]interface{}{
				"listener": map[string]interface{}{
					"filterChain": map[string]interface{}{
						"filter": map[string]interface{}{
							"name": "envoy.router",
							"subFilter": map[string]interface{}{
								"name": "envoy.router"}}}}}}

		got, err := replaceFilterChainName(out)
		if err != nil {
			t.Errorf("replaceFilterChainName() error = %v, expected no error", err)
			return
		}
		if !reflect.DeepEqual(got, out) {
			t.Errorf("replaceFilterChainName() got = %v, want %v", got, out)
		}

		out = map[string]interface{}{
			"match": map[string]interface{}{
				"listener": map[string]interface{}{
					"filterChain": map[string]interface{}{
						"filter": map[string]interface{}{
							"name": "envoy.router"}}}}}

		got, err = replaceFilterChainName(out)
		if err != nil {
			t.Errorf("replaceFilterChainName() error = %v, expected no error", err)
			return
		}
		if !reflect.DeepEqual(got, out) {
			t.Errorf("replaceFilterChainName() got = %v, want %v", got, out)
		}
	})

}
